using System;
using System.Collections.Generic;
using System.Text;
using System.Data.Common;
using System.Security;
using System.Collections;
using NBusiness.Utilities;
using System.Configuration;
using System.Threading;
using System.Data;
using NBusiness.Query;

namespace NBusiness.Data
{
    [Serializable]
    public abstract class CachedEntityBaseCollection<T> : 
        CollectionBase, 
        IEnumerable<T>, 
        IEnumerable, 
        IEntityCollection
        where T : EntityBase, new()
    {
        #region Constructors
        protected CachedEntityBaseCollection(CriteriaBase criteria) : this()
        {
            string connectionStringName = (string)Settings.Instance.GetSetting(SettingsType.ConnectionStringName, this.GetType());
            string connstr = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
            using (DbConnection cn = ((IDatabaseProvider)Settings.Instance.GetSetting(SettingsType.Provider, this.GetType())).GetConnection(connstr))
            {
                Fetch(cn, criteria);
            }
        }
        protected CachedEntityBaseCollection(DbConnection cn, CriteriaBase criteria)
            : this()
        {
            Fetch(cn, criteria);
        }
        private CachedEntityBaseCollection()
        {
            //Ensure static constructor has been called.
            T t = new T();
            Initialize();
        }
        #endregion

        #region Initialize
        /// <summary>
        /// Implement in partial class to allow user code to attach events and implement custom code.
        /// </summary>
        public virtual void Initialize()
        {
        }
        #endregion

        #region Authorization Rules
        /// <summary>
        /// Check to see if this user can Fetch this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanFetch()
        {
            return EntityBase.AuthorizationRules.CanFetch(typeof(T), Thread.CurrentPrincipal);
        }
        /// <summary>
        /// Check to see if this user can Insert this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanInsert()
        {
            return EntityBase.AuthorizationRules.CanInsert(typeof(T), Thread.CurrentPrincipal);
        }
        /// <summary>
        /// Check to see if this user can Update this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanUpdate()
        {
            return EntityBase.AuthorizationRules.CanUpdate(typeof(T), Thread.CurrentPrincipal);
        }
        /// <summary>
        /// Check to see if this user can Delete this object.
        /// </summary>
        /// <returns></returns>
        public virtual bool CanDelete()
        {
            return EntityBase.AuthorizationRules.CanDelete(typeof(T), Thread.CurrentPrincipal);
        }
        #endregion

        #region Fetch
        protected class FetchAllCriteria : CriteriaBase
        {
            public FetchAllCriteria() : base() { }

            public FetchAllCriteria(int startIndex, int maxRows, string sortField, SortOrder sortOrder)
                : base(startIndex, maxRows, sortField, sortOrder)
            { }

            public override bool Equals(object obj)
            {
                return obj is FetchAllCriteria;
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }

        protected virtual void Fetch(DbConnection cn, CriteriaBase criteria)
        {
            if (!CanFetch()) throw new SecurityException("Not Authorized to Fetch");

            CachedEntityBaseCollection<T> cached = CachedEntityBaseCollection<T>.GetCachedCollection(
                new CachedCollectionKey(criteria, this.GetType()));

            if (cached == null)
            {
                if (cn.State != ConnectionState.Open ) cn.Open();

                if (!OnFetching(cn, criteria))
                {
                    FetchCachedCollection(cn, criteria);
                    CachedEntityBaseCollection<T>.AddToCachedCollection(
                        new CachedCollectionKey(criteria, this.GetType()), this);

                    OnFetched(cn);
                }
            }
            else
            {
                InnerList.AddRange(cached);
            }
        }
        protected abstract void FetchCachedCollection(DbConnection cn, CriteriaBase criteria);
        /// <summary>
        /// Implment this event to handle pre fetch conditions. Setting the argument Cancel property
        /// to true will prevent the fetch from occurring and the Fetched event will not fire.
        /// </summary>
        public event EventHandler<FetchEventArgs> Fetching;
        protected virtual bool OnFetching(DbConnection cn, CriteriaBase criteria)
        {
            bool cancel = false;
            if (Fetching != null)
            {
                FetchEventArgs fetchArgs = new FetchEventArgs(cn, criteria);
                Fetching(this, fetchArgs);
                cancel = fetchArgs.Cancel;
            }
            return cancel;
        }
        /// <summary>
        /// Implement this event to add extra behavior during a fetch of this Entity on the same
        /// connection.
        /// </summary>
        public event EventHandler<EventArgs<DbConnection>> Fetched;
        protected virtual void OnFetched(DbConnection cn)
        {
            if (Fetched != null) Fetched(this, new EventArgs<DbConnection>(cn));
        }
        #endregion

        #region Load
        public virtual void Load(object sender, LoadEventArgs e)
        {
            OnLoading(e);
            LoadCachedEntityBaseCollection(sender, e);
            OnLoaded(e);
        }

        protected abstract void LoadCachedEntityBaseCollection(object sender, LoadEventArgs e);

        public event EventHandler<LoadEventArgs> Loading;
        protected virtual void OnLoading(LoadEventArgs e)
        {
            if (Loading != null) Loading(this, e);
        }

        public event EventHandler<LoadEventArgs> Loaded;
        protected virtual void OnLoaded(LoadEventArgs e)
        {
            if (Loaded != null) Loaded(this, e);
        }

        #endregion

        #region Cached Items

        private static Dictionary<CachedCollectionKey, object> _cache = new Dictionary<CachedCollectionKey, object>();
        public static CachedEntityBaseCollection<T> GetCachedCollection(CachedCollectionKey key)
        {
            object cached = null;

            if (System.Web.HttpContext.Current != null)
            {
                cached = System.Web.HttpContext.Current.Session[key.ToString()];
            }
            else
            {
                lock (_cache)
                {
                    if (_cache.ContainsKey(key))
                    {
                        cached = _cache[key];
                    }
                }
            }
            return (CachedEntityBaseCollection<T>)cached;
        }

        public static void AddToCachedCollection(CachedCollectionKey key, CachedEntityBaseCollection<T> cached)
        {
            if (System.Web.HttpContext.Current != null)
            {
                System.Web.HttpContext.Current.Session[key.ToString()] = cached;
            }
            else
            {
                lock (_cache)
                {
                    _cache.Add(key, cached);
                }
            }
        }
        #endregion

        #region Collection Methods
        public T this[int index]
        {
            get
            {
                return (T)InnerList[index];
            }
            set
            {
                InnerList[index] = value;
            }
        }
        public int IndexOf(T t)
        {
            return InnerList.IndexOf(t);
        }
        public bool Contains(T t)
        {
            return InnerList.Contains(t);
        }
        public T[] ToArray()
        {
            object[] objs = InnerList.ToArray();
            T[] ts = new T[objs.Length];
            Array.Copy(objs, ts, objs.Length);
            return ts;
        }
        #endregion
        
        #region IEnumerable<T> Members

        public new IEnumerator<T>  GetEnumerator()
        {
            foreach (T t in InnerList)
            {
                yield return t;
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            foreach (T t in InnerList)
            {
                yield return t;
            }
        }

        #endregion

        #region CachedCollectionKey
        public class CachedCollectionKey
        {
            private CriteriaBase _criteria;
            public CriteriaBase Criteria
            {
                get { return _criteria; }
            }
            private Type _collectionType;
            public Type CollectionType
            {
                get { return _collectionType; }
            }
            public CachedCollectionKey(CriteriaBase criteria, Type collectionType)
            {
                if (criteria == null)
                    throw new ArgumentNullException("criteria cannot be null");

                _criteria = criteria;
                _collectionType = collectionType;
            }
            public override bool Equals(object obj)
            {
                bool equals = false;
                if (obj is CachedCollectionKey)
                {
                    CachedCollectionKey key = obj as CachedCollectionKey;
                    equals = _criteria.Equals(key.Criteria) && _collectionType.Equals(key.CollectionType);
                }
                return equals;
            }

            public override int GetHashCode()
            {
                return ToString().GetHashCode();
            }

            public override string ToString()
            {
                return _criteria.ToString() + ", " + _collectionType.ToString();
            }
        } 
        #endregion

        #region IEntityCollection Members

        EntityBase IEntityCollection.this[int index]
        {
            get
            {
                return this[index];
            }
            set
            {
                if (!typeof(T).IsAssignableFrom(value.GetType()))
                    throw new ArgumentException("Invalid Type, must be " + typeof(T).ToString(), "value");

                this[index] = (T)value;
            }
        }

        public int IndexOf(EntityBase entity)
        {
            if (!typeof(T).IsAssignableFrom(entity.GetType()))
                throw new ArgumentException("Invalid Type, must be " + typeof(T).ToString(), "entity");

            return InnerList.IndexOf(entity);
        }

        public void Remove(EntityBase entity)
        {
            if (!typeof(T).IsAssignableFrom(entity.GetType()))
                throw new ArgumentException("Invalid Type, must be " + typeof(T).ToString(), "entity");

            InnerList.Remove(entity);
        }

        public void Add(EntityBase entity)
        {
            if (!typeof(T).IsAssignableFrom(entity.GetType()))
                throw new ArgumentException("Invalid Type, must be " + typeof(T).ToString(), "value");
            
            
        }

        public void Save()
        {
            throw new NotSupportedException("Cached collections cannot save");
        }
        #endregion
    }
}
